#include "bond.h"

// The invalid coupling (a nullptr).
const Coupling Bond::invalid = nullptr;
// The carbon coupling to it self.
const ivec4 Bond::SameAtom[1] = { ivec4("0 0 0 0") };

// The graphene nearest neighbour bonds. Only from A(B) atoms to B(A) atoms in the same (i,j)-plane.
const ivec4 Bond::NearestNeighbours[6] = {
	ivec4(" 0  0 0  1"),
	ivec4(" 0  1 0  1"),
	ivec4("-1  0 0  1"),
	ivec4(" 0  0 0 -1"),
	ivec4(" 1  0 0 -1"),
	ivec4(" 0 -1 0 -1")
};

// The bi-layer graphene dimer bonds across layers. Only from B atoms to A atoms in the bottom and top (i,j)-planes, respectfully.
const ivec4 Bond::DimerNeighbours[2] = {
	ivec4(" 0 0  1 -1"),
	ivec4(" 0 0 -1  1")
};

// The bi-layer graphene nearest neighbour dimer-to-non-dimer bonds across layers. Only from A atoms to A atoms or B to B in the bottom and top (i,j)-planes.
const ivec4 Bond::DimerNextNeighbours[6] = {
	ivec4(" 0  0  1 0"),
	ivec4(" 0  1  1 0"),
	ivec4("-1  0  1 0"),
	ivec4(" 0  0 -1 0"),
	ivec4(" 0 -1 -1 0"),
	ivec4(" 1  0 -1 0")
};

const ivec4 Bond::NonDimerNeighbours[6] = {
	ivec4(" 1 -1 -1 -1"),
	ivec4(" 1  0 -1 -1"),
	ivec4(" 0 -1 -1 -1"),
	ivec4("-1  1  1  1"),
	ivec4("-1  0  1  1"),
	ivec4(" 0  1  1  1")
};

// The default bond constructor returns an invalid bond.
Bond::Bond() : orig(nullptr),dest(nullptr),coupling(nullptr),couplingtype(nullptr) { }
// Overload constructor returns a bond depending of the Atoms a and n. Check validity using isValid().
Bond::Bond(const Atom &a,const Atom &n, const ivec4 &periodicvector) : orig(&a),dest(&n),coupling(nullptr),couplingtype(nullptr) { retrieveBond(a, n, periodicvector); }
	
// This getter function returns a pointer to the origin Atom.
const Atom* Bond::getOrigin() const { return orig; };
// This getter function returns a pointer to the destination Atom.
const Atom* Bond::getDestination() const { return dest; }
// This getter function returns the bond vector in xyz coordinates.
vec3 Bond::getXyz() const { return Atom::changeBasis(*coupling); }
// This returns the bond vector in xyz coordinates.
vec3 Bond::xyz() const { return Atom::changeBasis(*coupling); }

// This getter function returns the coupling vector.
Coupling Bond::getCoupling() const { return coupling; }
// This getter function returns the coupling type.
CouplingType Bond::getCouplingType() const { return couplingtype; }
// This convenience function test if the coupling is valid. Same as getCouplingType() == Bond::invalid.
bool Bond::isValid() const { return couplingtype != invalid; }

bool Bond::isBetweenSameAtom() const { return couplingtype == SameAtom; }
bool Bond::isBetweenInplaneNN() const { return couplingtype == NearestNeighbours; }
bool Bond::isBetweenDimerNN() const { return couplingtype == DimerNeighbours; }
bool Bond::isBetweenNonDimerNN() const { return couplingtype == NonDimerNeighbours; }
bool Bond::isBetweenDimerNonDimerNN() const { return couplingtype == DimerNextNeighbours; }

// This function returns a bond between two atoms. If the bond is not defined an invalid bond is returned.
void Bond::retrieveBond(const Atom &a, const Atom &n, const ivec4 &periodicvector)
{
	if(a.h() == n.h() && a.l() == n.l())
		return retrieveSameSublatticeBond(a,n,periodicvector);
	else if(a.h() == n.h() && a.l() != n.l())
		return retrieveDifferentSublatticeBond(a,n,periodicvector);
	else if(a.h() != n.h() && a.l() == n.l())
		return retrieveStackedSameSublatticeBond(a,n,periodicvector);
	else if(a.h() != n.h() && a.l() != n.l())
		return retrieveStackedDifferentSublatticeBond(a,n,periodicvector);
}
// This convinience function only looks for NearestNeighbour bonds.
void Bond::retrieveSameSublatticeBond(const Atom &a, const Atom &n, const ivec4 &periodicvector)
{
	if(equal(Atom::difference(a,n),ivec4(SameAtom[0]-periodicvector)))
	{
		coupling = SameAtom;
		couplingtype = SameAtom;
	}
}
// This convinience function only looks for NearestNeighbour bonds.
void Bond::retrieveDifferentSublatticeBond(const Atom &a, const Atom &n, const ivec4 &periodicvector)
{
	if(a.sublattice() == Atom::A)
	{
		for(int b = 0; b < 3; b++) // The indices of AtoB bonds in NearestNeighbours.
		{
			if(equal(Atom::difference(a,n),ivec4(NearestNeighbours[b]-periodicvector)))
			{
				coupling = NearestNeighbours+b;
				couplingtype = NearestNeighbours;
				return;
			}
		}
	}
	else
	{
		for(int b = 3; b < 6; b++) // The indices of AtoB bonds in NearestNeighbours.
		{
			if(equal(Atom::difference(a,n),ivec4(NearestNeighbours[b]-periodicvector)))
			{
				coupling = NearestNeighbours+b;
				couplingtype = NearestNeighbours;
				return;
			}
		}
	}
}
// This convinience function only looks for DimerNextNeighbours and NonDimerNeighbours bonds.
void Bond::retrieveStackedSameSublatticeBond(const Atom &a, const Atom &n, const ivec4 &periodicvector)
{
	for(int b = 0; b < 6; b++) // The indices of AtoA bonds in DimerNextNeighbours.
	{
		if(equal(Atom::difference(a,n),ivec4(DimerNextNeighbours[b]-periodicvector)))
		{
			coupling = DimerNextNeighbours+b;
			couplingtype = DimerNextNeighbours;
			return;
		}
	}
}
// This convinience function only looks for DimerNeighbours and NonDimerNeighbours bonds.
void Bond::retrieveStackedDifferentSublatticeBond(const Atom &a, const Atom &n, const ivec4 &periodicvector)
{
	if(a.sublattice() == Atom::A)
	{
		// The index of AtoB bonds in DimerNeighbours.
		if(equal(Atom::difference(a,n),ivec4(DimerNeighbours[1]-periodicvector)))
		{
			coupling = DimerNeighbours+1;
			couplingtype = DimerNeighbours;
			return;
		}
		
		for(int b = 3; b < 6; b++) // The indices of AtoB bonds in NonDimerNeighbours.
		{
			if(equal(Atom::difference(a,n),ivec4(NonDimerNeighbours[b]-periodicvector)))
			{
				coupling = NonDimerNeighbours+b;
				couplingtype = NonDimerNeighbours;
				return;
			}
		}
	}
	else
	{
		// The index of BtoA bonds in DimerNeighbours.
		if(equal(Atom::difference(a,n),ivec4(DimerNeighbours[0]-periodicvector)))
		{
			coupling = DimerNeighbours+0;
			couplingtype = DimerNeighbours;
			return;
		}
		
		for(int b = 0; b < 3; b++) // The indices of BtoA bonds in NonDimerNeighbours.
		{
			if(equal(Atom::difference(a,n),ivec4(NonDimerNeighbours[b]-periodicvector)))
			{
				coupling = NonDimerNeighbours+b;
				couplingtype = NonDimerNeighbours;
				return;
			}
		}
	}
}
// This convenience function behaves like an equality operator (==).
 bool Bond::equal(const ivec4 &v1, const ivec4 &v2)
 {
	 static const size_t size = 4;
	 bool result[size];
	 result[0:size] = (v1.memptr()[0:size] == v2.memptr()[0:size]);
	 return __sec_reduce_and(result[0:size]);
 }